#include <slothttpd/file.hpp>
#include <slothttpd/logger.hpp>

#include <cstring>
#include <cerrno>

#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>

namespace slothttpd {

file::cache_t file::cache;

file::file() :
    fd_(-1),
    ref_count_(0),
    from_pool_(false)
{
}

file::open_result file::get_direct_io(generic_pool &pool, const char *path, file_ptr &ref)
{
    file_ptr p = from_pool(pool);
    auto result = p->open(path, O_DIRECT);
    
    if (result == success) {
        ref = p;
    }
    
    return result;
}

file::open_result file::get_cached(const char *path, file_ptr &ref)
{
    struct comparator {
        bool operator()(const char *p, const file &f) const
        {
            return p == f.path_;
        }
        
        bool operator()(const file &f, const char *p) const
        {
            return p == f.path_;
        }
    };
    
    open_result result = success;
    file_ptr p;
    
    auto it = cache.find(path, comparator());
    if (it == cache.end()) {
        p = new file();
        result = p->open(path, 0);
        
        if (result == success) {
            cache.insert(*p);
        }
    } else {
        p = &*it;
    }
    
    if (result == success) {
        ref = p;
    }
    
    return result;
}

file::open_result file::get_nocache(generic_pool &pool, const char *path, file_ptr &ref)
{
    file_ptr p = from_pool(pool);
    auto result = p->open(path, 0);
    
    if (result == success) {
        ref = p;
    }
    
    return result;
}

file_ptr file::from_pool(generic_pool &pool)
{
    file_ptr ret = pool.construct<file>();
    ret->from_pool_ = true;
    
    return ret;
}

file::open_result file::open(const char *path, int flags)
{
    close();
    
    flags |= O_CLOEXEC | O_LARGEFILE | O_RDONLY;
    
    struct stat64 file_stats;
    
    fd_ = ::open(path, flags);
    if (fd_ >= 0) {
        if (::fstat64(fd_, &file_stats) != 0) {
            logger() << strerror(errno) << std::endl;
            
            if (::close(fd_) != 0) {
                logger() << strerror(errno) << std::endl;
            }
            
            return unknown_error;
        }
        
        size_       = file_stats.st_size;
        last_mod_   = file_stats.st_mtim.tv_sec;
        
        if (!S_ISREG(file_stats.st_mode)) {
            return not_found;
        }
        
        path_ = path;
        
        return success;
    }
    
    switch (errno) {
    case ENOENT:
        return not_found;
        
    case EACCES:
        return forbidden;
        
    default:
        return unknown_error;
    }
}

void file::close()
{
    if (fd_ >= 0) {
        if (::close(fd_) != 0) {
            logger() << strerror(errno) << std::endl;
        }
    }
}

void intrusive_ptr_add_ref(file *r) noexcept
{
    r->ref_count_++;
}

void intrusive_ptr_release(file *r) noexcept
{
    r->ref_count_--;
    if (r->ref_count_ == 0) {
        r->close();
        
        if (!r->from_pool_)
            delete r;
    }
}

}
